﻿//////////////////////////////////////////////////////////////////
//
// Logger.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using log4net.Ext.Audit;
using System.Threading;

namespace Utilities
{
   public class Logger
   {
      //////////////////////////////////////////////////////////////////////////

      static Logger()
      {
         CommonLogger = "emily";

         // Load the configuration
         string fullPath = Path.Combine(System.Environment.CurrentDirectory, "log4net.config");
         if (File.Exists(fullPath))
         {
            // Use log4net.config, if it's there
            FileInfo configFile = new FileInfo(fullPath);
            log4net.Config.XmlConfigurator.Configure(configFile);
         }
         else
         {
            // Otherwise use the app.config
            log4net.Config.XmlConfigurator.Configure();
         }
      }

      //////////////////////////////////////////////////////////////////////////

      public Logger()
      {
         AuditLogManager.ThisDeclaringType = typeof(Logger);
         logger_ = AuditLogManager.GetLogger(CommonLogger);
      }

      //////////////////////////////////////////////////////////////////////////

      public Logger(string name)
      {
         try
         {
            // Force log4net to regard us as the "top" level in the debug hierarchy, so that the filename will
            // be output as the assembly that calls us and it won't just show our filename as the source
            // of all debug output
            AuditLogManager.ThisDeclaringType = typeof(Logger);

            // And get the logger for the class that is calling us
            logger_ = AuditLogManager.GetLogger(name);
         }
         catch (System.Exception ex)
         {
            System.Diagnostics.Debug.WriteLine("caught exception creating logger '"
               + name + "': " + ex.ToString());
         }
      }

      //////////////////////////////////////////////////////////////////////////

      public virtual string Exception(Exception ex)
      {
         return Exception(null, ex);
      }

      public virtual string Exception(SocketException ex)
      {
         return Exception(String.Format("socket error '{0}' ({1}):", ex.SocketErrorCode.ToString(), ex.SocketErrorCode), ex);
      }

      public virtual string Exception(string context, Exception ex, params object[] args)
      {
         string result = string.Empty;
         try
         {
            if (null == context)
            {
               context = "caught exception, no context supplied\n";
            }
            else
            {
               try
               {
                  context = String.Format(context, args);
               }
               catch
               {
               }
            }
            if (!context.EndsWith("\n"))
            {
               context += "\n";
            }
            result = String.Concat(context, Formatter.Exception(ex));
            if (null != logger_)
            {
               Error(result);
            }
            else
            {
               try { System.Diagnostics.Debug.WriteLine(context); }
               catch { }
               try { System.Diagnostics.Debug.WriteLine(ex.ToString()); }
               catch { }
            }
         }
         catch { }
         return result;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Audit(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Audit(text);
         }
      }
      public void Audit(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Audit(text, ex);
         }
      }
      public void AuditFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.AuditFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void AuditFlush() { }
      public bool AuditEnabled { get { return logger_.IsAuditEnabled; } set { } }
      public Stream AuditStream { get { return GetStream(StreamType.Audit); } }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Raw(string className, string methodName, StreamType level, string filename, int line, string text)
      {
         if ((null != logger_) && (null != text))
         {
            log4net.Core.LoggingEventData loggingEventData = new log4net.Core.LoggingEventData();
            loggingEventData.Domain = AppDomain.CurrentDomain.ToString();
            loggingEventData.Identity = Thread.CurrentPrincipal.Identity.Name;
            loggingEventData.Level =
               (StreamType.Audit == level) ? log4net.Core.Level.Debug :
               (StreamType.Debug == level) ? log4net.Core.Level.Debug :
               (StreamType.Info == level) ? log4net.Core.Level.Info :
               (StreamType.Warn == level) ? log4net.Core.Level.Warn :
               (StreamType.Error == level) ? log4net.Core.Level.Error :
               (StreamType.Fatal == level) ? log4net.Core.Level.Fatal :
               new log4net.Core.Level((int) level, string.Concat("custom_", level.ToString()));
            loggingEventData.LocationInfo = new log4net.Core.LocationInfo(className, methodName, filename, line.ToString());
            loggingEventData.LoggerName = logger_.Logger.Name;
            loggingEventData.Message = text;
            loggingEventData.ThreadName = Thread.CurrentThread.Name;
            loggingEventData.TimeStamp = DateTime.Now;
            loggingEventData.UserName = Environment.UserName;
            log4net.Core.LoggingEvent loggingEvent = new log4net.Core.LoggingEvent(loggingEventData);
            logger_.Logger.Log(loggingEvent);
         }
      }
      public void RawFormat(string className, string methodName, StreamType level, string filename,
         int line, string text, params object[] args)
      {
         Raw(className, methodName, level, filename, line, string.Format(text, args));
      }
      public void RawFlush() { }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Debug(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Debug(text);
         }
      }
      public void Debug(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Debug(text, ex);
         }
      }
      public void DebugFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.DebugFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void DebugFlush() { }
      public bool DebugEnabled { get { return logger_.IsDebugEnabled; } set { } }
      public Stream DebugStream { get { return GetStream(StreamType.Debug); } }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Info(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Info(text);
         }
      }
      public void Info(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Info(text, ex);
         }
      }
      public void InfoFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.InfoFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void InfoFlush() { }
      public bool InfoEnabled { get { return logger_.IsInfoEnabled; } set { } }
      public Stream InfoStream { get { return GetStream(StreamType.Info); } }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Warn(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Warn(text);
         }
      }
      public void Warn(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Warn(text, ex);
         }
      }
      public void WarnFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.WarnFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void WarnFlush() { }
      public bool WarnEnabled { get { return logger_.IsWarnEnabled; } set { } }
      public Stream WarnStream { get { return GetStream(StreamType.Warn); } }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Error(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Error(text);
         }
      }
      public void Error(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Error(text, ex);
         }
      }
      public void ErrorFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.ErrorFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void ErrorFlush() { }
      public bool ErrorEnabled { get { return logger_.IsErrorEnabled; } set { } }
      public Stream ErrorStream { get { return GetStream(StreamType.Error); } }

      ////////////////////////////////////////////////////////////////////////
      //
      public void Fatal(string text)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Fatal(text);
         }
      }
      public void Fatal(string text, Exception ex)
      {
         if ((null != logger_) && (null != text))
         {
            logger_.Fatal(text, ex);
         }
      }
      public void FatalFormat(string text, params object[] args)
      {
         if ((null != logger_) && (null != text))
         {
            try
            {
               logger_.FatalFormat(text, args);
            }
            catch (System.Exception ex)
            {
               Exception(text, ex);
            }
         }
      }
      public void FatalFlush() { }
      public bool FatalEnabled { get { return logger_.IsFatalEnabled; } set { } }
      public Stream FatalStream { get { return GetStream(StreamType.Fatal); } }

      private Stream GetStream(StreamType streamType)
      {
         LoggerStream output = null;
         try
         {
            // If we do not yet have a stream for this type of debug message, then we create one
            if (!streams_.TryGetValue(streamType, out output))
            {
               // Get the output function for the given type of stream (debug, fatal etc)
               switch (streamType)
               {
                  case StreamType.Audit:
                     output = new LoggerStream(Audit);
                     break;

                  case StreamType.Debug:
                     output = new LoggerStream(Debug);
                     break;

                  case StreamType.Info:
                     output = new LoggerStream(Info);
                     break;

                  case StreamType.Warn:
                     output = new LoggerStream(Warn);
                     break;

                  case StreamType.Error:
                     output = new LoggerStream(Error);
                     break;

                  case StreamType.Fatal:
                     output = new LoggerStream(Fatal);
                     break;

                  case StreamType.None:
                  default:
                     System.Diagnostics.Debug.Write(String.Format("unknown logger stream type {0}", streamType));
                     return null;
               }
               streams_.Add(streamType, output);
            }
         }
         catch (System.Exception ex)
         {
            Exception(ex);
         }
         return output;
      }

      ////////////////////////////////////////////////////////////////////////

      public static string CommonLogger
      {
         get
         {
            return commonLoggerName_;
         }
         set
         {
            commonLoggerName_ = value;
            commonLogger_ = AuditLogManager.GetLogger(commonLoggerName_);
         }
      }

      ////////////////////////////////////////////////////////////////////////

      public IAuditLog logger_ = null;

      // We use a standard logger to simplify the .config file.
      private static string commonLoggerName_ = "logger4net";
      private static IAuditLog commonLogger_ = AuditLogManager.GetLogger("logger4net");
      
      ////////////////////////////////////////////////////////////////////////
      //
      public enum StreamType
      {
         None,
         Audit,
         Debug,
         Info,
         Warn,
         Error,
         Fatal
      }
      private delegate void LoggerStreamOutput(string text);
      private class LoggerStream : Stream
      {
         public LoggerStream(LoggerStreamOutput output)
         {
            output_ = output;
         }

         public override bool CanRead { get { return false; } }
         public override bool CanSeek { get { return false; } }
         public override bool CanTimeout { get { return false; } }
         public override bool CanWrite { get { return true; } }
         public override long Length { get { return 0; } }
         public override long Position { get { return 0; } set { throw new NotSupportedException(); } }
         public override void Flush()
         {
            if (0 < orphan_.Length)
            {
               output(orphan_);
               orphan_ = "";
            }
         }
         public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
         public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
         public override void SetLength(long value) { throw new NotSupportedException(); }
         public override void Write(byte[] buffer, int offset, int count)
         {
            if (0 == count)
            {
               return;
            }
            StringBuilder text = new StringBuilder(orphan_.Length + count);
            text.Append(orphan_);
            text.Append(Encoding.UTF8.GetString(buffer, offset, count));
            int textLength = text.Length;
            if (0 == textLength)
            {
               return;
            }
            char terminator = text[textLength - 1];

            string[] lines = text.ToString().Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
            int linesLength = lines.Length;
            if (('\n' != terminator) || ('\r' != terminator))
            {
               --linesLength;
               orphan_ = lines[linesLength];
               if (0 == linesLength)
               {
                  return;
               }
            }
            for (int index = 0; index < linesLength; ++index)
            {
               output(lines[index]);
            }
         }

         public LoggerStreamOutput output { get { return output_; } }
         private LoggerStreamOutput output_ = null;
         private string orphan_ = "";
      }
      private Dictionary<StreamType, LoggerStream> streams_ = new Dictionary<StreamType, LoggerStream>();
   }
}
